#include "swagme.h"
#include "config.h"
#include "Game.h"
#include "Display.h"
#include "Player.h"
#include "Scene.h"
#include "Swag.h"
#include "sound.h"
#include "Nerd.h"
#include "Babe.h"

#include <iostream>
#include <algorithm>
using std::cout;
using std::cerr;
using std::endl;

// 2 minutes for each screen (for now)
#define TIME_LEFT (10*60)

// TODO: put this somewhere into the XML file (maybe a <chrome> element?)
const char* szStatusAreaBackgroundImage = "resources/graphics/status-area.png";

Game::Game() : 
    mTimeLeftInSec(TIME_LEFT), 
    mStartTimeInMs(SDL_GetTicks()), 
    mpDisplay(new Display()),
    mpPlayer(NULL),
    mpConfig(InitConfig("resources/wad.xml")),
    mpCurLevel(NULL),
    mbResortRenderables(true),
    sound(NULL),
    mbUpKeyPressed(false),
    mbRightKeyPressed(false),
    mbDownKeyPressed(false),
    mbLeftKeyPressed(false),
    theWall(NULL),
    mpScene(NULL),
    mpStatusAreaSurf(NULL),
    mbStatusDirty(true),
    mLastPhotoTimeInMs(mStartTimeInMs),
    mpBlingMarkerSurf(NULL),
    mbPhotoKeyPressed(false),
    mnCurLevelIndex(0)
{
  if(!mpDisplay) {
    cerr << "Error!  Could not initialize Display" << endl;
    exit(4);
  }
  cout << "Game and Display initialized." << endl;
  Init();
}

Game::~Game()
{
  UnloadConference();

  // release the audio device
  if (sound) delete sound;

  // delete all the sprites now
  for( map<string,Sprite*>::iterator spriteIt = mSprites.begin(); spriteIt != mSprites.end(); ++spriteIt) {
    Sprite* s = spriteIt->second;
    delete s;
  }
  mSprites.clear();

  // delete all effects
  for( EffectIter effectIt = mEffects.begin(); effectIt != mEffects.end(); ++effectIt) {
    if (effectIt->second) {
      delete effectIt->second;
      effectIt->second = NULL;
    }
  }
  mEffects.clear();
  
  // destroy chrome
  // TODO: One day have a DestroyChrome() as counterpart to InitChrome()
  if(mpStatusAreaSurf) { 
    SDL_FreeSurface(mpStatusAreaSurf); 
    mpStatusAreaSurf = NULL;
  }
  Photo::ClearBorder(); // free the border image surfaces
  if(mpBlingMarkerSurf) {
    SDL_FreeSurface(mpBlingMarkerSurf);
    mpBlingMarkerSurf = NULL;
  }

  if(mpPlayer) {
    delete mpPlayer;
    mpPlayer = NULL;
  }

  if(mpConfig) {
    delete mpConfig;
    mpConfig = NULL;
  }

  if(mpScene) {
    delete mpScene;
    mpScene = NULL;
  }

  if(mpDisplay) {
    delete mpDisplay;
    mpDisplay = NULL;
  }

  if(theWall) {
    delete theWall;
    theWall = NULL;
  }

  cout << "Game and Display destroyed." << endl;
}

// TODO: Maybe figure out a better way (C-style macro? config file?) of creating
// these type-point associations 
int Game::getSwagPoints(const sm_swag_type& type) {
    if(sm_swag_pen == type) { return 5; }
    else if(sm_swag_mug == type) { return 8; }
    else if(sm_swag_hat == type) { return 12; }
    else if(sm_swag_yoyo == type) { return 16; }
    else if(sm_swag_tshirt == type) { return 20; }
    return 10;
}

// read in all resources
// find the first conference
// set up player position
void Game::Init()
{
  InitChrome();
  InitSprites();
  InitSounds();
  InitConferences();

  // - get player sprite
  if(mSprites.find("player-sprite") == mSprites.end())  { 
    cerr << "Error!  Could not find player-sprite <sprite> in XML" << endl; 
    exit(-1); 
  }
  // - create player
  mpPlayer = new Player(mSprites["player-sprite"], 0, 0, 0);

  LoadConference();

  dumpColMap();
}

void Game::InitSprites()
{
  cout << "in InitSprites" << endl;
  // get the resources element
  XMLNode* pResources = mpConfig->getElementByTagName( "resources" );
  if(!pResources) { return; }

  // load effects
  NodeList effectNodes;
  int numEffects = pResources->getElementsByTagName("effect", &effectNodes);
  if(!numEffects) {
    cerr << "Could not find any <effect> elements in wad.xml, boooring" << endl;
  }
  for(NodeListIter effectIt = effectNodes.begin(); effectIt != effectNodes.end(); ++effectIt) {
    XMLNode* node = *effectIt;

    if(node->id().length() == 0) { cerr << "Error!  Effect id is blank" << endl; exit(200711151); }
    if(node->attr["src"].length() == 0) {
      cerr << "Error!  Effect '" << node->id() << "' does not have a src attribute" << endl;
      exit(200711152);
      }
    Effect *effect = new Effect(node->attr["src"]);
    mEffects[node->attr["id"]] = effect;
  }

  // set up sprite resources
  NodeList spriteNodes;
  int numSprites = pResources->getElementsByTagName("sprite", &spriteNodes);
  if(!numSprites) {
    cerr << "Error!  Could not find any <sprite> elements in wad.xml" << endl;
    exit(2004);
  }
  for(NodeListIter spriteIt = spriteNodes.begin(); spriteIt != spriteNodes.end(); ++spriteIt) {
    XMLNode* node = *spriteIt;
    Sprite* sprite;

    if(node->id().length() == 0) { cerr << "Error!  Sprite id is blank" << endl; exit(2005); }
    if(node->attr["src"].length() == 0) { cerr << "Error!  Sprite '" << node->id() << "' does not have a src attribute" << endl; exit(2006); }
    if(node->attr["shadow"].length() == 0) {
      sprite = new Sprite(node->attr["src"].c_str(), NULL, &mEffects);
    } else {
      sprite = new Sprite(node->attr["src"].c_str(), node->attr["shadow"].c_str(), &mEffects);
    }
    mSprites[node->id()] = sprite;
  }
}

void Game::InitSounds()
{
  cout << "in InitSounds" << endl;
  // get the resources element
  XMLNode* pResources = mpConfig->getElementByTagName( "resources" );
  if(!pResources) { return; }

  sound = new Sound();
  NodeList soundNodes;
  int numSounds = pResources->getElementsByTagName("sound", &soundNodes);
  for(NodeListIter soundIt = soundNodes.begin(); soundIt != soundNodes.end(); ++soundIt) {
    XMLNode* node = *soundIt;
    // Sound* sound;

    if(node->id().length() == 0) { cerr << "Error!  Sound id is blank" << endl; exit(2007); }
    if(node->attr["src"].length() == 0) { cerr << "Error!  Sound '" << node->id() << "' does not have a src attribute" << endl; exit(2008); }
    sound->Load(node->attr["src"],node->attr["id"]);
  }
  cout << "Loaded Sounds" << endl;
}

void Game::InitScene()
{
  theWall = new DummyWall;
  mpScene->InitScene();

  // CollisionMap construction
  mColMap.theWall = theWall;
  mColMap.cellInPixels = 8; // each square represents an 8x8 pixel region
  mColMap.width = ATOI(mpCurLevel->attr["width"])/mColMap.cellInPixels;
  mColMap.height = ATOI(mpCurLevel->attr["height"])/mColMap.cellInPixels;

  crunchCollisionMap(mpScene->CollisionMap);
}

void Game::InitConferences() 
{
  // get all levels (order matters in the XML doc)
  int numLevels = mpConfig->getElementsByTagName("conference", &mAllLevels);
  if(!numLevels) {
    cerr << "Error!  Could not find any <conference> elements in wad.xml" << endl;
    exit(2000);
  }
}

// TODO: need to load in the endCondition of a conference
// TODO: need to understand and evaluate the endCondition within Game::Update() periodically
bool Game::LoadConference()
{
  size_t confNum = mnCurLevelIndex;
  cout << "mnCurLevelIndex = " << confNum << endl;
  if(confNum >= mAllLevels.size()) {
    return false;
  }

  // find the appropriate conference
  mpCurLevel = mAllLevels[confNum];
  if(!mpCurLevel) {
    cerr << "Error!  No suitable levels found" << endl;
    exit(-1);
  }
  int lev_width = ATOI(mpCurLevel->attr["width"]);
  int lev_height = ATOI(mpCurLevel->attr["height"]);
  cout << "Conference level was '" << mpCurLevel->attr["name"] << "'" << " (" << lev_width << "," << lev_height << ")" << endl;

  // set up background map
  if(mpScene) { delete mpScene; mpScene = NULL; }
  mpScene = new Scene(mpCurLevel->attr["map"].c_str(), mpCurLevel->attr["collisionMap"].c_str(), mpCurLevel->attr["floor"].c_str());
  if(!mpScene) {
    cerr << "Error!  Could not initialize scene" << endl;
    exit(5);
  }
  // InitScene() initializes the colmap (requires mpCurLevel to be set)
  InitScene();

  // initialize player
  // - get initial player position
  XMLNode* ps = mpCurLevel->getElementByTagName("playerStart");
  if(!ps ) {
    cerr << "Error!  Could not find <playerStart> element in first <conference> element" << endl;
    exit(-1);
  }
  double unitsPerMs = ATOF(ps->attr["unitsPerSec"]) / 1000.0;
  if(unitsPerMs == 0.0) { cout << "Error!  unitsPerSec not set on <playerStart/> element" << endl; }
  cout << "Player's unitsPerMs = " << unitsPerMs << endl;
  int playerFacing = ATOI(ps->attr["facing"]);
  if(playerFacing < MIN_DIR || playerFacing > MAX_DIR) { 
    cout << "WARNING:  XML file had an invalid value for playerStart facing: " << playerFacing << endl;
    playerFacing = sm_dir_down;
  }
  mpPlayer->SetX(ATOI(ps->attr["x"]));
  mpPlayer->SetY(ATOI(ps->attr["y"]));
  mpPlayer->SetSpeed(unitsPerMs);
  mpPlayer->SetFacing( (sm_direction)playerFacing );
  mRenderables.push_back(mpPlayer); // not owned
  mColMap.addTouchableObject( mpPlayer ); // not owned
  
  // read in swag from <conference> element
  NodeList swagNodes;
  mpCurLevel->getElementsByTagName("swag", &swagNodes);
  for(NodeListIter swagIt = swagNodes.begin(); swagIt != swagNodes.end(); ++swagIt) {
    XMLNode* node = *swagIt;
    cout << "Found <swag> with type=" << node->attr["type"] << endl;

    sm_swag_type swagType;
    // TODO: Maybe figure out if there's a better C-style macro way of making
    // all of these string-to-type associations
    if(node->attr["type"] == "PEN") { swagType = sm_swag_pen; }
    else if(node->attr["type"] == "HAT") { swagType = sm_swag_hat; }
    else if(node->attr["type"] == "YOYO") { swagType = sm_swag_yoyo; }
    else if(node->attr["type"] == "TSHIRT") { swagType = sm_swag_tshirt; }
    else if(node->attr["type"] == "MUG") { swagType = sm_swag_mug; }
    else {
      cerr << "Warning!  Ignoring <swag> element with unknown swag type '" << node->attr["type"] << "'" << endl;
      continue;
    }

    if(mSprites.find(node->attr["sprite"]) == mSprites.end()) {
      cerr << "Warning!  Ignoring <swag> element with unknown sprite '" << mSprites[node->attr["sprite"]] << "'" << endl;
      continue;
    }
    
    // add this id to our ID map
    vector<string>& vec = mSpriteIds[swagType];
    if( find(vec.begin(), vec.end(), node->attr["sprite"]) == vec.end() ) {
      vec.push_back(node->attr["sprite"]);
      cout << "Added '" << node->attr["sprite"] << "' to mSpriteIds" << endl;
    }

    int x = 0;
    int y = 0;
    bool bFoundLocation = false;
    while(!bFoundLocation) {
      x = ATOI(node->attr["x"]);
      y = ATOI(node->attr["y"]);
      if(node->attr["x"] == "RANDOM") {
        x = random(30, lev_width-30);
      }
      if(node->attr["y"] == "RANDOM") {
        y = random(30, lev_width-30);
      }

      if( node->attr["x"] == "RANDOM" || node->attr["y"] == "RANDOM") {
        // TODO:  Swag is hard-coded to 12x12, we need to change this 
        // code here if it ever is not so.  This means that we have to
        // check 4 colmap indices (swag takes up a 2x2 square in colmap)
        int colx = x/mColMap.cellInPixels;
        int coly = y/mColMap.cellInPixels;
        int index = coly*mColMap.width + colx;
        if(!mColMap.map[index] && !mColMap.map[index+1] && 
          !mColMap.map[index+mColMap.width] && !mColMap.map[index+mColMap.width+1]) 
        {
          bFoundLocation = true;
        }
      }
      else {
        bFoundLocation = true;
      }

    } // while(!bFoundLocation)

    Swag* aSwag = new Swag(mSprites[node->attr["sprite"]], x, y, swagType, getSwagPoints(swagType));
    mSwaggage.push_back( aSwag ); // owned
    mRenderables.push_back( aSwag ); // not owned
    mColMap.addTouchableObject( aSwag ); // not owned

  }

  // read in babes from <conference> element
  NodeList babeNodes;
  mpCurLevel->getElementsByTagName("babe", &babeNodes);
  for(NodeListIter babeIt = babeNodes.begin(); babeIt != babeNodes.end(); ++babeIt) {
    XMLNode* node = *babeIt;
    cout << "Found <babe>" << endl;

    if(mSprites.find(node->attr["sprite"]) == mSprites.end()) {
      cerr << "Error!  Could not find sprite '" << mSprites[node->attr["sprite"]] << "'" << endl;
      exit(-1);
    }

    int x = 0;
    int y = 0;
    double unitsPerMs = 0.0;
    double generosity = 0;
    bool bFoundLocation = false;
    while(!bFoundLocation) {
      x = ATOI(node->attr["x"]);
      y = ATOI(node->attr["y"]);
      unitsPerMs = ATOF(node->attr["unitsPerSec"]) / 1000.0;
      generosity = ATOF(node->attr["generosity"]);

      if(node->attr["x"] == "RANDOM") {
        x = random(30, lev_width-30);
      }
      if(node->attr["y"] == "RANDOM") {
        y = random(30, lev_width-30);
      }
      if(generosity == 0) {
        generosity = BABE_DEFAULT_GENEROSITY;
      }

      if(node->attr["y"] == "RANDOM" || node->attr["y"] == "RANDOM") {
        // TODO:  Babes are hard-coded to 12x12, we need to change this 
        // code here if it ever is not so.  This means that we have to
        // check 4 colmap indices (npc takes up a 2x2 square in colmap)
        int colx = x/mColMap.cellInPixels;
        int coly = y/mColMap.cellInPixels;
        int index = coly*mColMap.width + colx;
        if(!mColMap.map[index] && !mColMap.map[index+1] && 
          !mColMap.map[index+mColMap.width] && !mColMap.map[index+mColMap.width+1]) 
        {
          bFoundLocation = true;
        }
      }
      else {
        bFoundLocation = true;
      }
    } // while(!bFoundLocation)

    sm_direction babeFacing;
    if(node->attr["facing"] == "RANDOM") {
      babeFacing = (sm_direction)(random(MIN_DIR, MAX_DIR));
    }
    else {
      int tempFacing = ATOI(node->attr["facing"]);
      if(tempFacing < MIN_DIR || tempFacing > MAX_DIR) {
        cerr << "Warning!  XML file had invalid value for NPC facing: " << babeFacing << endl;
        tempFacing = sm_dir_down;
      }
      babeFacing = (sm_direction)(tempFacing);
    }

    Babe* aBabe = new Babe(mSprites[node->attr["sprite"]], x, y, unitsPerMs, generosity);
    aBabe->SetFacing( babeFacing );
    mBabes.push_back( aBabe ); // owned
    mRenderables.push_back( aBabe ); // not owned
    mColMap.addTouchableObject( aBabe ); // not owned
  }

  // read in nerds from <conference> element
  NodeList nerdNodes;
  mpCurLevel->getElementsByTagName("nerd", &nerdNodes);
  for(NodeListIter nerdIt = nerdNodes.begin(); nerdIt != nerdNodes.end(); ++nerdIt) {
    XMLNode* node = *nerdIt;
    cout << "Found <nerd>" << endl;

    if(mSprites.find(node->attr["sprite"]) == mSprites.end()) {
      cerr << "Error!  Could not find sprite '" << mSprites[node->attr["sprite"]] << "'" << endl;
      exit(-1);
    }

    int x = 0;
    int y = 0;
    double unitsPerMs;
    double bodyCount;
    bool bFoundLocation = false;
    while(!bFoundLocation) {
      x = ATOI(node->attr["x"]);
      y = ATOI(node->attr["y"]);
      unitsPerMs = ATOF(node->attr["unitsPerSec"]) / 1000.0;
      bodyCount = ATOF(node->attr["bodyCount"]);

      if(node->attr["x"] == "RANDOM") {
        x = random(30, lev_width-30);
      }
      if(node->attr["y"] == "RANDOM") {
        y = random(30, lev_width-30);
      }

      if(node->attr["y"] == "RANDOM" || node->attr["y"] == "RANDOM") {
        // TODO:  Nerds are hard-coded to 12x12, we need to change this 
        // code here if it ever is not so.  This means that we have to
        // check 4 colmap indices (npc takes up a 2x2 square in colmap)
        int colx = x/mColMap.cellInPixels;
        int coly = y/mColMap.cellInPixels;
        int index = coly*mColMap.width + colx;
        if(!mColMap.map[index] && !mColMap.map[index+1] && 
          !mColMap.map[index+mColMap.width] && !mColMap.map[index+mColMap.width+1]) 
        {
          bFoundLocation = true;
        }
      }
      else {
        bFoundLocation = true;
      }
    } // while(!bFoundLocation)

    sm_direction nerdFacing;
    if(node->attr["facing"] == "RANDOM") {
      nerdFacing = (sm_direction)(random(MIN_DIR, MAX_DIR));
    }
    else {
      int tempFacing = ATOI(node->attr["facing"]);
      if(tempFacing < MIN_DIR || tempFacing > MAX_DIR) {
        cerr << "Warning!  XML file had invalid value for Nerd facing: " << nerdFacing << endl;
        tempFacing = sm_dir_down;
      }
      nerdFacing = (sm_direction)(tempFacing);
    }

    Nerd* aNerd = new Nerd(mSprites[node->attr["sprite"]], x, y, unitsPerMs, bodyCount);
    aNerd->SetFacing( nerdFacing );
    mNerds.push_back( aNerd ); // owned
    mRenderables.push_back( aNerd ); // not owned
    mColMap.addTouchableObject( aNerd ); // not owned
  } // nerds

  return true;
}

// This function will tear down all objects created for a conference
// (photos, swag, babes, nerds)
void Game::UnloadConference()
{
    /* // we are not going to do this, so that mpCurLevel is always pointing at a valid object
  // only do this if mpCurLevel is set
  if(!mpCurLevel) {
    cerr << "UnloadConference() called twice" << endl;
    exit(14000);
  }
  */

  // wipe this collection clean
  mRenderables.clear();

  if (mColMap.map) { delete[] mColMap.map; mColMap.map = NULL; }

  for (PhotoIter iter=mPhotos.begin();  iter != mPhotos.end();  iter++) {
    delete *iter;
  }
  mPhotos.clear();
  
  for (unsigned int idx = 0; idx < mSwaggage.size(); idx++) {
    delete mSwaggage[idx];
  }
  mSwaggage.clear();

  for (unsigned int idx = 0; idx < mBabes.size(); idx++) {
    delete mBabes[idx];
  }
  mBabes.clear();

  for (unsigned int idx = 0; idx < mNerds.size(); idx++) {
    delete mNerds[idx];
  }
  mNerds.clear();

  // see comment at top of function
//  mpCurLevel = NULL;
}


/*
 * Return the pixel value at (x, y)
 * NOTE: The surface must be locked before calling this!
 * (from the SDL docs at file:///usr/share/doc/packages/SDL-devel/html/guidevideo.html#AEN90)
 */
Uint32 getpixel(SDL_Surface *surface, int x, int y)
{
  int bpp = surface->format->BytesPerPixel;
  /* Here p is the address to the pixel we want to retrieve */
  Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp;

  switch(bpp) {
    case 1:
      return *p;

    case 2:
      return *(Uint16 *)p;

    case 3:
      if(SDL_BYTEORDER == SDL_BIG_ENDIAN)
        return p[0] << 16 | p[1] << 8 | p[2];
      else
        return p[0] | p[1] << 8 | p[2] << 16;

    case 4:
      return *(Uint32 *)p;

    default:
      return 0;       /* shouldn't happen, but avoids warnings */
  }
}

void Game::crunchCollisionMap(SDL_Surface *s) {
  int size = mColMap.width * mColMap.height;
  mColMap.map = new TouchableObject*[size];
//  cout << "Crunching: " << hex;
  for (int ix=0, iy=0; (ix<s->w) && (iy<s->h); ix++,iy++) {
//    cout << getpixel(s,ix, iy) << " ";
  }

  // ensure that s->w and s->h correspond to the same size as mColMap.map
  if ((mColMap.width * mColMap.cellInPixels != s->w) || (mColMap.height * mColMap.cellInPixels != s->h)) {
    cout << "Error: mColMap dimensions don't match image dimensions mColMap.width:" << mColMap.width << " s->w: " << s->w << " mColMap.height: " << mColMap.height << " s->h: " << s->h << endl;
    exit(-1);
  }

  // We read the collisionMap one cell at a time. So we start at (0,0) then read all pixels up to (8,8). This determines one entry in mColMap.map. After that we move on to the cell at (8,0) to (16,8)
  for (unsigned int cx=0; cx < mColMap.width; cx++) {
    for (unsigned int cy=0; cy < mColMap.height; cy++) {
      int pixelCount=0;
      // read in one cell from the full scale map
      for (unsigned int ix=0; ix < mColMap.cellInPixels; ix++) {
        for (unsigned int iy=0; iy < mColMap.cellInPixels; iy++) {
          // TODO: this is just the colour that the Gimp chose for transparent in this image but I think there's an alpha channel that could be used instead
          // get the current pixel
          if (getpixel(s, (cx * mColMap.cellInPixels) + ix,  (cy * mColMap.cellInPixels) + iy)== 0) {
          } else {
            pixelCount++;
          }
        }
      }
      // compute the map value from the full scale map
      if (pixelCount < 32) {
        mColMap.map[cx + cy * mColMap.width]=NULL;
      } else {
        mColMap.map[cx + cy * mColMap.width]=theWall;
      }
    }
  }
  cout << dec << endl;
}

void Game::dumpColMap() {
  cout << "Collision Map: " << endl;
  for (unsigned int iy=0; iy < mColMap.height; iy++) {
    for (unsigned int ix=0; ix < mColMap.width; ix++) {
      TouchableObject* obj = mColMap.map[ix + iy * mColMap.width];
      if (obj) {
        sm_cd_object type = obj->getObjectType();
        switch(type) {
          case sm_cdo_wall: cout << "X"; break;
          case sm_cdo_player: cout << "P"; break;
          case sm_cdo_nerd: cout << "N"; break;
          case sm_cdo_babe: cout << "B"; break;
          case sm_cdo_swag: cout << "S"; break;
        } // switch(type)
      } else {
        cout << " ";
      }
    }
    cout << endl;
  }
}

// primarily what this is does is update the time left
// and display to the status area
void Game::Update(Uint8* keystate, Uint32 prevTick, Uint32 thisTick)
{
  if(prevTick == thisTick || IsGameOver()) { return; }
  // in milliseconds
  int frameTime = (int)thisTick - (int)prevTick;
//  cout << "Frame time in ms: " << frameTime << endl;

  if(mTimeLeftInSec > 0) {
    int oldTime = mTimeLeftInSec;
    mTimeLeftInSec = TIME_LEFT - (SDL_GetTicks() - mStartTimeInMs)/1000;
    if(mTimeLeftInSec != oldTime) {
//    cout << "Time Left (s) = " << mTimeLeftInSec << " seconds." << endl;
    }
  }

  // keyboard controls:

  // directions:
  //  w,a,d,s or up, left, down, right arrows or num pad equiv
  // if we want the player to just keep moving until they change direction, then this can be set to true or removed
  // rjr: I kind of like that - it's more like pacman.  jms:  I vote no...
  bool moveKeyPressed=false; 
  
  // save the previous position of the player
  mpPlayer->updatePrevPos();

  static Uint32 dumpPressedTime = 0; // rjr - debug sound dump
  static bool mbDumpPressed = false; // rjr - debug sound dump
  if (keystate[SDLK_o] && !mbDumpPressed) {
    // debug: sound dump
    mbDumpPressed = true;
    dumpPressedTime = SDL_GetTicks();
    sound->dump();
  } else if (mbDumpPressed && SDL_GetTicks() > dumpPressedTime + 1000) {
    mbDumpPressed = false;
  }

  if (keystate[SDLK_w] || keystate[SDLK_UP] || keystate[SDLK_KP8]) {
    // every time the up key is pressed, add a keypress to the stack
    if(!mbUpKeyPressed) { 
      // add an "up" key press to the stack
      mKeyPressStack.push_front(sm_dir_up);
      mbUpKeyPressed = true;
    }
  } 
  else { 
    // every time the up key is released, remove the keypress from the stack
    if(mbUpKeyPressed) {
      // remove any "up" key presses from the stack
      for(KeyIter it = mKeyPressStack.begin(); it != mKeyPressStack.end(); ++it) {
        if(*it == sm_dir_up) { mKeyPressStack.erase(it); break;}
      }
      mbUpKeyPressed = false;
    }
  }

  if (keystate[SDLK_a] || keystate[SDLK_LEFT] || keystate[SDLK_KP4]) {
    if(!mbLeftKeyPressed) { 
      mKeyPressStack.push_front(sm_dir_left);
      mbLeftKeyPressed = true;
    }
  } 
  else { 
    if(mbLeftKeyPressed) {
      for(KeyIter it = mKeyPressStack.begin(); it != mKeyPressStack.end(); ++it) {
        if(*it == sm_dir_left) { mKeyPressStack.erase(it); break;}
      }
      mbLeftKeyPressed = false;
    }
  }

  if (keystate[SDLK_s] || keystate[SDLK_DOWN] || keystate[SDLK_KP2]) {
    if(!mbDownKeyPressed) { 
      mKeyPressStack.push_front(sm_dir_down);
      mbDownKeyPressed = true;
    }
  } 
  else { 
    if(mbDownKeyPressed) {
      for(KeyIter it = mKeyPressStack.begin(); it != mKeyPressStack.end(); ++it) {
        if(*it == sm_dir_down) { mKeyPressStack.erase(it); break;}
      }
      mbDownKeyPressed = false;
    }
  }

  if (keystate[SDLK_d] || keystate[SDLK_RIGHT] || keystate[SDLK_KP6]) {
    if(!mbRightKeyPressed) { 
      mKeyPressStack.push_front(sm_dir_right);
      mbRightKeyPressed = true;
    }
  }
  else { 
    if(mbRightKeyPressed) {
      for(KeyIter it = mKeyPressStack.begin(); it != mKeyPressStack.end(); ++it) {
        if(*it == sm_dir_right) { mKeyPressStack.erase(it); break;}
      }
      mbRightKeyPressed = false;
    }
  }

  // set our facing to the top-most key-press in our stack
  if(mKeyPressStack.size() > 0) {
    mpPlayer->SetFacing(mKeyPressStack.front());
    moveKeyPressed = true;
  }

  // set up SDL_Rects for use with CDR
  rectf curPos_f, newPos_f;

  // ======================================================================= //
  // Do Player Actions
  // ======================================================================= //
  // space bar - take a picture
  if (keystate[SDLK_SPACE]) {
    // minimum time between photos is PHOTO_DELAY_MS ms
    // but the spacebar cannot be held down for repeats
    if (mLastPhotoTimeInMs + PHOTO_DELAY_MS < SDL_GetTicks() && !mbPhotoKeyPressed) {
      mbPhotoKeyPressed = true;
      rectf playerPos = mpPlayer->getBox();
      Photo *photo = new Photo();
      // TODO: consts, not 16
      int status = photo->Shoot(mpDisplay->GetScreen(), (int)playerPos.x - 50, (int)playerPos.y - 50);
      mPhotos.push_back(photo); // delete photos when mPhotos is disposed
      sound->Play("camera");
      mpPlayer->AddEffect("camera-flash");
      cout << "Taking photo (status " << status << ")" << endl;
      // reset photo timer
      mLastPhotoTimeInMs = thisTick;
    }
  }
  else {
    mbPhotoKeyPressed = false;
  }

  // try to move
  if (moveKeyPressed) {

    curPos_f = mpPlayer->getBox();

    // new Pos is adjusted by facing and speed
    newPos_f.x = curPos_f.x; newPos_f.y = curPos_f.y;
    double numPixelsMove = mpPlayer->mUnitsPerMs * (double)frameTime;
    switch(mpPlayer->GetFacing()) {
      case sm_dir_up: newPos_f.y -= numPixelsMove; break;
      case sm_dir_left: newPos_f.x -= numPixelsMove; break;
      case sm_dir_down: newPos_f.y += numPixelsMove; break;
      case sm_dir_right: newPos_f.x += numPixelsMove; break;
    }

    // if we are not within our sub-pixels, then do CDR
    if((int)curPos_f.x != (int)newPos_f.x || (int)curPos_f.y != (int)newPos_f.y) {
      CollisionEvent event;
      SDL_Rect curPos, newPos;

      curPos.x = (int)curPos_f.x; curPos.y = (int)curPos_f.y;
      curPos.w = (int)curPos_f.w; curPos.h = (int)curPos_f.h;
      newPos.x = (int)newPos_f.x; newPos.y = (int)newPos_f.y;
      
      mColMap.removeTouchableObject( mpPlayer );
      moveRectWithCdr( mColMap, curPos, newPos, event);

      // first do cdr for swag
      if(event.result == sm_cdr_partial_move || event.result == sm_cdr_cannot_move) {
        if(event.cdrObjectType == sm_cdo_swag) {
          Swag* pSwag = dynamic_cast<Swag*>(event.cdrObject);
          mpPlayer->PickupSwag( *pSwag );
          mbStatusDirty = true;
          // NOTE: remove it from mRenderables before we delete it so we don't get a segfault!
          mRenderables.erase(std::find(mRenderables.begin(), mRenderables.end(), pSwag));
          // also, remove it from the col map
          mColMap.removeTouchableObject( pSwag );
          mSwaggage.erase(std::find(mSwaggage.begin(), mSwaggage.end(), pSwag));
          delete pSwag;
          sound->Play("getswag");
          cout << "Got swag, bling now at " << mpPlayer->GetBling() << endl;
        }
        // adjust bling
        // TODO: latch this so that the blind deduction doesn't happen repeatedely (do like photos)
        //       In fact, we should have a set of times stored.  Each tick/update different bits of
        //       logic can check the current time against the stored-times for whatever action
        //       they need to allow next.  For photos, we set mNextPhotoTime = current_time + 2000
        //       then each tick we don't allow a photo to be taken unless mNextPhotoTime < current_time
        //       Similar logic should apply for each nerd bling reduction.
        else if(event.cdrObjectType == sm_cdo_nerd) {
          Nerd* nerd = dynamic_cast<Nerd*>(event.cdrObject);
          cout << "Player bling was " << mpPlayer->GetBling() << " but collided with nerd that has a BodyCount of "
                << nerd->GetBodyCount() << endl;
          mpPlayer->SetBling( mpPlayer->GetBling() - 2.0*nerd->GetBodyCount() );
          cout << "Player bumped into nerd, bling now at " << mpPlayer->GetBling() << endl;
        }
      }

      if(event.result == sm_cdr_success || event.result == sm_cdr_partial_move) {
        //cout << "Was able to move, adjusting player now" << endl;
        if (!sound->IsPlaying("walk")) sound->Play("walk");
        if(event.result == sm_cdr_success) {
          mpPlayer->move(newPos_f.x, newPos_f.y);
        }
        else {
          mpPlayer->move(event.finalX, event.finalY);
        }
        mbResortRenderables = true;
      }
      else if(event.result == sm_cdr_failure) {
        cerr << "CDR failure" << endl;
        mbResortRenderables = true;
      }
      else { // could not move
        if (sound->IsPlaying("walk")) sound->Stop("walk");
        // reset our floating-point values to the current position
        mpPlayer->move(curPos.x, curPos.y);
      }
      mColMap.addTouchableObject( mpPlayer );
    } // do CDR
    else { // adjust sub-pixel position
      double newX = mpPlayer->getBox().x;
      double newY = mpPlayer->getBox().y;
      switch(mpPlayer->GetFacing()) {
        case sm_dir_up: newY -= numPixelsMove; break;
        case sm_dir_left: newX -= numPixelsMove; break;
        case sm_dir_down: newY += numPixelsMove; break;
        case sm_dir_right: newX += numPixelsMove; break;
      }

      mpPlayer->move(newX, newY);
    }
  } //  if (moveKeyPressed)
  else {
    if (sound->IsPlaying("walk")) sound->Stop("walk");
  }
  // ======================================================================= //

  // ======================================================================= //
  // Do Babe Actions
  // ======================================================================= //
  for(BabeIter it = mBabes.begin(); it != mBabes.end(); ++it) {
    Babe* babe = *it;

    curPos_f = babe->getBox();

    // new Pos is adjusted by facing and speed
    newPos_f.x = curPos_f.x; newPos_f.y = curPos_f.y;
    double babeTweakedUnitsPerMs = babe->mUnitsPerMs;
    
    // slow down for nearby high bling player, they want your picture
    if (babe->isNear(mpPlayer)){
      // when bling is high, babes let players get close
      const double bling = mpPlayer->GetBling();
      const Uint32 ticks = thisTick;
      if ((bling > BLING_WAIT_LEVEL) && (mLastPhotoTimeInMs + PHOTO_AFTERGLOW_DELAY_MS < ticks)) {
        babeTweakedUnitsPerMs -= bling/10;
        if (babeTweakedUnitsPerMs < 0) {
          if(babe->nextPosingOpportunity() <= thisTick) {
            babe->startPosing(thisTick, (int)(bling*3000/5.0));
          }
          if(babe->isPosing(thisTick)) {
            babeTweakedUnitsPerMs = 0;
            // if the babe is stopping then face forward
            babe->SetFacing(sm_dir_down);
          }
          else {
            babeTweakedUnitsPerMs += bling/10;
          }
        }
//        cout << "near player, adjusted speed from " << babe->mUnitsPerMs << " down to " << babeTweakedUnitsPerMs << endl;
      } else if (bling < BLING_RUN_LEVEL) {
        // when bling is low, babes run
        babeTweakedUnitsPerMs -= bling/10; // - because bling is < 0
        // cap maximum speed boost from low bling
        if (babeTweakedUnitsPerMs > 0.4) babeTweakedUnitsPerMs = 0.4;
//      cout << "near player, adjusted speed up to " << babeTweakedUnitsPerMs << endl;
      }
    }

    double numPixelsMove = babeTweakedUnitsPerMs * (double)frameTime;
    switch(babe->GetFacing()) {
      case sm_dir_up: newPos_f.y -= numPixelsMove; break;
      case sm_dir_left: newPos_f.x -= numPixelsMove; break;
      case sm_dir_down: newPos_f.y += numPixelsMove; break;
      case sm_dir_right: newPos_f.x += numPixelsMove; break;
    }

    if((int)curPos_f.x != (int)newPos_f.x || (int)curPos_f.y != (int)newPos_f.y) {
      CollisionEvent event;
      SDL_Rect curPos, newPos;
      curPos.x = (int)babe->getBox().x; curPos.y = (int)babe->getBox().y;
      curPos.w = (int)babe->getBox().w; curPos.h = (int)babe->getBox().h;
      newPos.x = (int)newPos_f.x; newPos.y = (int)newPos_f.y;

      mColMap.removeTouchableObject( babe );
      moveRectWithCdr( mColMap, curPos, newPos, event );
  
      if(event.result == sm_cdr_success || event.result == sm_cdr_partial_move) {
        if(event.result == sm_cdr_success) {
          babe->move(newPos_f.x, newPos_f.y);
        }
        else {
          babe->move(event.finalX, event.finalY);
        }
        mbResortRenderables = true;
      }
      else {
        // pick a random number between 1 and 4
        babe->SetFacing( (sm_direction)random(MIN_DIR,MAX_DIR) );
      }
  
      mColMap.addTouchableObject( babe );
    }

    // random chance for swag throw
    // frameTime * babe->generosity / 1000.0 = % chance this frame of a swag throw
    if( random(0,100000) <= (int)(frameTime * babe->getGenerosity()) ) {
      // for each surrounding empty square, there is a 40% chance
      // that the square will be populated with some swag
      // TODO: do we need to check for overflow/underflow here? Otherwise we need to check later operations on x & y in case they're negative or out of bounds
      const int babex = (int)(babe->getLeft()+babe->getWidth()/2)/mColMap.cellInPixels;
      const int babey = (int)(babe->getTop()+babe->getHeight()/2)/mColMap.cellInPixels;
      bool bPlayedSound = false;
      // NOTE: swag is 12x12, so we look at colmap squares in at least a 3-square radius
      // around the babe.  If this ever changes, we will need to adjust the loop.
      for(int y = babey-3; y <= babey+3; ++y) {
        for(int x = babex-3; x <= babex+3; ++x) {
          if(random(0,100) < 40) {
            // randomly pick swag type
            sm_swag_type swagType = (sm_swag_type)random(MIN_SWAG_TYPE, MAX_SWAG_TYPE);
            SDL_Rect rc; rc.x = x; rc.y = y; rc.w = 2; rc.h = 2;
            if(mColMap.isEmptyCol(rc)) {
              // pick a sprite ID
              vector<string>& vec = mSpriteIds[swagType];
              if(vec.size() > 0) {
                string id = vec[random(0,(int)vec.size()-1)];

                Swag* aSwag = new Swag(mSprites[id], x*mColMap.cellInPixels, 
                                      y*mColMap.cellInPixels, swagType, getSwagPoints(swagType));
                mSwaggage.push_back( aSwag ); // owned
                mRenderables.push_back( aSwag ); // not owned
                mColMap.addTouchableObject( aSwag ); // not owned
                mbResortRenderables = true;

                if(!bPlayedSound) {
                  sound->Play("swagthrow");
                  bPlayedSound = true;
                }
              } // found a sprite id
            } // colmap was empty
          } // 50% chance
        } // x loop
      } // y loop
    } // swag throw
  } // for each babe
  // ======================================================================= //

  // ======================================================================= //
  // Do Nerd Actions
  // ======================================================================= //
  for(NerdIter it = mNerds.begin(); it != mNerds.end(); ++it) {
    Nerd* nerd = *it;

    curPos_f = nerd->getBox();

    // new Pos is adjusted by facing and speed
    newPos_f.x = curPos_f.x; newPos_f.y = curPos_f.y;
    double numPixelsMove = nerd->mUnitsPerMs * (double)frameTime;
    switch(nerd->GetFacing()) {
      case sm_dir_up: newPos_f.y -= numPixelsMove; break;
      case sm_dir_left: newPos_f.x -= numPixelsMove; break;
      case sm_dir_down: newPos_f.y += numPixelsMove; break;
      case sm_dir_right: newPos_f.x += numPixelsMove; break;
    }

    if((int)curPos_f.x != (int)newPos_f.x || (int)curPos_f.y != (int)newPos_f.y) {
      CollisionEvent event;
      SDL_Rect curPos, newPos;
      curPos.x = (int)nerd->getBox().x; curPos.y = (int)nerd->getBox().y;
      curPos.w = (int)nerd->getBox().w; curPos.h = (int)nerd->getBox().h;
      newPos.x = (int)newPos_f.x; newPos.y = (int)newPos_f.y;
  
      mColMap.removeTouchableObject( nerd );
      moveRectWithCdr( mColMap, curPos, newPos, event );
  
      // first do cdr for swag
      if(event.result == sm_cdr_partial_move || event.result == sm_cdr_cannot_move) {
        if(event.cdrObjectType == sm_cdo_swag) {
          Swag* pSwag = dynamic_cast<Swag*>(event.cdrObject);
          mRenderables.erase(std::find(mRenderables.begin(), mRenderables.end(), pSwag));
          mColMap.removeTouchableObject( pSwag );
          mSwaggage.erase(std::find(mSwaggage.begin(), mSwaggage.end(), pSwag));
          delete pSwag;
          sound->Play("getswag");
        } // if nerd collided with swag
        else if(event.cdrObjectType == sm_cdo_player) {
//          cout << "Player bling was " << mpPlayer->GetBling() << " but nerd with BodyCount of "
//                << nerd->GetBodyCount() << " bumped into the player" << endl;
          mpPlayer->SetBling( mpPlayer->GetBling() - nerd->GetBodyCount() );
        }
      } // if nerd hit something
  
      if(event.result == sm_cdr_success || event.result == sm_cdr_partial_move) {
        if(event.result == sm_cdr_success) {
          nerd->move(newPos_f.x, newPos_f.y);
        }
        else {
          nerd->move(event.finalX, event.finalY);
        }
        mbResortRenderables = true;

        // give a 4% chance that the nerds will change direction here
        if(random(0,100) < 4) { 
          nerd->SetFacing( (sm_direction)random(MIN_DIR,MAX_DIR) );
        }
      }
      else {
        // pick a random number between 1 and 4
        nerd->SetFacing( (sm_direction)random(MIN_DIR,MAX_DIR) );
      }
  
      mColMap.addTouchableObject( nerd );
    }
  } // for each Nerd
  // ======================================================================= //

//  dumpColMap();
  // each tick, we check out end conditions
  // simple one for now - if 7 photos, then we're done
  if(mPhotos.size() >= 7) {
    this->UnloadConference();
    ++mnCurLevelIndex;
    if(!LoadConference()) {
        cout << "Last level was loaded, game is over." << endl;
        mTimeLeftInSec = 0;
    }
    else {
      mRenderables.push_back(mpPlayer); // TODO: find a better place for this eventually
    }

  }

  DrawScreen();
} // Game::Update()

bool Game::IsGameOver() { 
  return (mTimeLeftInSec <= 0); 
}


class TouchableObjectDepthComparison
{
    public:
        bool operator()(TouchableObject* const &first,
                        TouchableObject* const &second) const
        {
          double bottomFirst = first->getBottom();
          double bottomSecond = second->getBottom();
          if(bottomFirst == bottomSecond) { return (first->getLeft() < second->getLeft()); }
          return bottomFirst < bottomSecond;
        }
};

void Game::DrawScreen()
{
  // sort all renderable objects by y value so they will draw back to front
  // only sort after any onscreen object moves
  if (mbResortRenderables) {
    // cout << "sort " << mRenderables.size() << endl;
    sort(mRenderables.begin(),mRenderables.end(),TouchableObjectDepthComparison());
    // cout << endl;
    mbResortRenderables = false;
  }
  
  mpDisplay->LockScreen();

  DrawChrome();

  // draw the floor
  DrawFloor();

  // draw shadows for all TouchableObjects
  for (unsigned int idx = 0; idx < mRenderables.size(); ++idx) {
    mRenderables[idx]->Draw(mpDisplay->GetScreen(), true);
  }

  // draw the map
  DrawForeground();

  // draw all TouchableObjects
  for (unsigned int idx = 0; idx < mRenderables.size(); ++idx) {
    mRenderables[idx]->Draw(mpDisplay->GetScreen(), false);
  }

  DrawStatus();

  // flip the screen now that all rendering is complete 
  mpDisplay->UnlockScreen();
  mpDisplay->Flip();
}

void Game::InitChrome()
{
  XMLNode* node;

  cout << "in InitChrome" << endl;
  mpStatusAreaSurf = IMG_Load(szStatusAreaBackgroundImage);
  if (!mpStatusAreaSurf) {
    cerr << "Unable to load status area image: " << SDL_GetError() << endl;
    exit(1);
  }
  cout << "  loaded status area background image" << endl;

  node = mpConfig->getElementById("photo-border");
  if (node) {
    Photo::SetBorder(node->attr["src"]);
  } else {
    cerr << "Error! No photo-border <chrome> element found in wad.xml" << endl;
    exit(2015);
  }
  cout << "  loaded photo border image" << endl;

  node = mpConfig->getElementById("bling-marker");
  if(node) {
    mpBlingMarkerSurf = IMG_Load(node->attr["src"].c_str());
    if (!mpBlingMarkerSurf) {
      cerr << "Unable to load bling marker image: " << SDL_GetError() << endl;
      exit(200706241);
    }
  } else {
    cerr << "Error! No bling-marker <chrome> element found in wad.xml" << endl;
      exit(200706242);
  }
  cout << "  loaded bling marker image" << endl;
}

// TODO: This should probably be removed one day...
void Game::DrawChrome()
{
  // temporary background 
  SDL_Surface* scr = mpDisplay->GetScreen();
  SDL_Rect rcMap = { 0, 0, SCREEN_SIZE_X - STATUS_SIZE_X, SCREEN_SIZE_Y };
  SDL_FillRect(scr, &rcMap, SDL_MapRGB(scr->format, 0, 0, 0)); // black
}

// this contains the foreground items (booths, tables, chairs, etc)
void Game::DrawForeground()
{
  if(mpScene->Map) {
    SDL_BlitSurface(mpScene->Map, NULL, mpDisplay->GetScreen(), &mpScene->MapRect);
  }
}

void Game::DrawFloor()
{
  if(mpScene->Floor) {
    SDL_BlitSurface(mpScene->Floor, NULL, mpDisplay->GetScreen(), &mpScene->MapRect);
  }
}

void Game::DrawStatus()
{
  int status;
  // only update status once a second
  static Sint32 oldTime = 0;
  if(!mbStatusDirty && mTimeLeftInSec == oldTime) { return; }
  oldTime = mTimeLeftInSec;
  mbStatusDirty = false;

  if(!mpDisplay || !mpPlayer) { return; }

  SDL_Rect rcStatus = { STATUS_POS_X, STATUS_POS_Y, STATUS_SIZE_X, STATUS_SIZE_Y };
  SDL_BlitSurface(mpStatusAreaSurf, NULL, mpDisplay->GetScreen(), &rcStatus);

  // render bling marker
  {
    SDL_Rect rcBM = {BLINGMARKER_POS_X, BLINGMARKER_POS_Y};
    // bling ranges from -5 to +5 - therefore BLINGGAUGE_SIZE_X spans an interval of 10.0
    rcBM.x += (int)(mpPlayer->GetBling() * (double)((BLINGGAUGE_SIZE_X/2)/5));
    SDL_BlitSurface(mpBlingMarkerSurf, NULL, mpDisplay->GetScreen(), &rcBM);
  } // done bling marker

  char szTimeStr[30];
  int time_x = STATUS_POS_X + 20; int time_y = STATUS_POS_Y + 65;
  SDL_Color black = {0,0,0,255}; 
  SDL_Color time_color = {0,0,0,255}; // black by default
  if(mTimeLeftInSec > 0) {
    sprintf(szTimeStr, "Time Left: %d:%02d", mTimeLeftInSec/60, mTimeLeftInSec%60);
  }
  else {
    sprintf(szTimeStr, "Game over, chump!");
    time_color.r = 255; // red
  }

//  char szPlayerStr[30];
//  int player_x = STATUS_POS_X + 10; int player_y = STATUS_POS_Y + 120;
//  sprintf(szPlayerStr, "Pos: (%d,%d)", mpPlayer->getLeft(), mpPlayer->getTop());

  char szPlayerScore[20];
  int player_points_y = time_y + 20;
  sprintf(szPlayerScore, "Points: %d", mpPlayer->GetPoints());

  char szLocation[40];
  sprintf(szLocation, "(%s)", mpCurLevel->attr["location"].c_str());

  int cx = STATUS_POS_X + (STATUS_SIZE_X/2);

  // rendering calls go here, should cluster by font and size
  mpDisplay->SetCurrentFont("resources/fonts/VeraBd.ttf", 14);
  mpDisplay->DrawText(mpCurLevel->attr["name"].c_str(), black, 
                      cx, STATUS_POS_Y + 20, sm_disp_centerh);
  mpDisplay->SetCurrentFont("resources/fonts/Vera.ttf", 12);
  mpDisplay->DrawText(szLocation, black, cx, STATUS_POS_Y + 40, sm_disp_centerh);

  mpDisplay->SetCurrentFont("resources/fonts/Vera.ttf", 14);
  mpDisplay->DrawText(szTimeStr, time_color, cx, time_y, sm_disp_centerh);
//  mpDisplay->DrawText(szPlayerStr, time_color, player_x, player_y);

  int midx = STATUS_POS_X+(STATUS_SIZE_X/2);

  mpDisplay->DrawText(szPlayerScore, time_color, midx, player_points_y, sm_disp_centerh);

  // render swag pile
  {
    mpDisplay->DrawText("Swag Pile", black, midx, 160, sm_disp_centerh);
    int count = 0;
    int swagNum = 0;
    const int left = STATUS_POS_X + 50;
    const int top = 200;
    const int incy = 30;
    const int incx = 70;
    char szSwagCountStr[20];
    for(int swagTypeInt = MIN_SWAG_TYPE; swagTypeInt <= MAX_SWAG_TYPE; swagTypeInt++) {
      sm_swag_type swagType = (sm_swag_type)(swagTypeInt);
      count = mpPlayer->GetSwagCount(swagType);
      if(count > 0) { 
//        cout << "Found a count for swag type '" << swagType << "' = " << count << endl;
        if(mSpriteIds.find(swagType) == mSpriteIds.end()) {
          cerr << "Could not find sprite for swag pile for type " << swagType << endl;
          continue;
        }
        Sprite* pSprite = mSprites[ mSpriteIds[swagType][0] ];

        // render the sprite
        pSprite->Draw(0,0,mpDisplay->GetScreen(),
                      left+incx*(swagNum%2),top+incy*(swagNum/2));

        sprintf(szSwagCountStr, "x %d", count);
        mpDisplay->DrawText(szSwagCountStr, black, left+incx*(swagNum%2)+15, top+incy*(swagNum/2)-15);

        ++swagNum;
      }
    }
  } // done swag pile

  // render photo bucket
  if (0)
  {
    mpDisplay->DrawText("Photos", black, midx, 375, sm_disp_centerh);
    const int leftcol = STATUS_POS_X + 20;
    const int middlecol = leftcol + 50;
    const int rightcol = middlecol + 50;
    
    const int toprow = 390;
    const int middlerow = 420;
    const int bottomrow = 480;

    PhotoIter iter = mPhotos.begin();
    if (mPhotos.end() == iter) {return;}
    
    // top row
    status = (*iter)->Draw(mpDisplay->GetScreen(), leftcol, toprow, false);
    cout << "Showing photo (status " << status << ") " << endl;
    ++iter;
    if (mPhotos.end() == iter) {return;}
    status = (*iter)->Draw(mpDisplay->GetScreen(), middlecol, toprow, false);
    cout << "Showing photo (status " << status << ") " << endl;
    ++iter;
    if (mPhotos.end() == iter) {return;}
    status = (*iter)->Draw(mpDisplay->GetScreen(), rightcol, toprow, false);
    cout << "Showing photo (status " << status << ") " << endl;
    ++iter;
    if (mPhotos.end() == iter) {return;}
    
    // middle
    status = (*iter)->Draw(mpDisplay->GetScreen(), middlecol, middlerow, true);
    cout << "Showing photo (status " << status << ") " << endl;
    ++iter;
    if (mPhotos.end() == iter) {return;}
    
    // bottom row
    status = (*iter)->Draw(mpDisplay->GetScreen(), leftcol, bottomrow, false);
    cout << "Showing photo (status " << status << ") " << endl;
    ++iter;
    if (mPhotos.end() == iter) {return;}
    status = (*iter)->Draw(mpDisplay->GetScreen(), middlecol, bottomrow, false);
    cout << "Showing photo (status " << status << ") " << endl;
    ++iter;
    if (mPhotos.end() == iter) {return;}
    status = (*iter)->Draw(mpDisplay->GetScreen(), rightcol, bottomrow, false);
    cout << "Showing photo (status " << status << ") " << endl;
    ++iter;
    if (mPhotos.end() == iter) {return;}
  } else if (0) {
    mpDisplay->DrawText("Photos", black, midx, 375, sm_disp_centerh);
    const int top = 390;
    const int left = STATUS_POS_X + 20;
    
    int posx = left;
    int posy = top;
    
    bool big = false;

    int row=0;
    for (PhotoIter iter = mPhotos.begin(); iter != mPhotos.end(); ++iter) {
      status = (*iter)->Draw(mpDisplay->GetScreen(), posx, posy, big);
      posx += PHOTO_BORDER_SIZE_X;
      // divide photos into rows
      if ((posx + PHOTO_BORDER_SIZE_X + 10 > STATUS_POS_X + STATUS_SIZE_X) || big) {
        posx = left;
        posy += PHOTO_BORDER_SIZE_Y;
        row++;
        big = ((1==row) ? true : false);
      }
      cout << "Showing photo (status " << status << ") " << endl;
    }
  } else {
    mpDisplay->DrawText("Photos", black, midx, 375, sm_disp_centerh);
    const int top = 390;
    const int left = STATUS_POS_X + 20;
    
    int posx = left;
    int posy = top;
    
    bool big = false;

    int index = 0;
    for (; (index + 1 < (int)mPhotos.size()) && (index < 6) ; ++index) {
      status = mPhotos[index]->Draw(mpDisplay->GetScreen(), posx, posy, false);
      posx += PHOTO_DISPLAY_AREA_X;
      // divide photos into rows
      if (posx + PHOTO_DISPLAY_AREA_X + 10 > STATUS_POS_X + STATUS_SIZE_X) {
        posx = left;
        posy += PHOTO_DISPLAY_AREA_Y + PHOTO_DISPLAY_AREA_Y;
      }
    }
    // latest photo on top of the pile
    if ((index < (int)mPhotos.size()) && (index < 7)) {
      status = mPhotos[index]->Draw(mpDisplay->GetScreen(), midx - PHOTO_DISPLAY_AREA_X, top + PHOTO_DISPLAY_AREA_Y / 2, true);
    }
  }
  
}

XMLNode* Game::InitConfig(const char* szConfigFilename) {
  return loadConfig(szConfigFilename);
}
